﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Drawing;
using Vortex.Utils;
using Vortex.Debugging;

namespace Vortex.SceneFramework {

	/// <summary>
	/// ViewNode is extension which restricts form of node as rectangle. Also ViewNode Location property defines is left,top corner.
	/// Origin point is read-only and forced to be a center of ViewNode. Rotation and Scaling will be applied to the center of ViewNode.
	/// ViewNode is a base for all of GUI nodes
	/// </summary>
	public abstract class ViewNode : Node {
		private Vector2 _size;
		private Rect _margin;
		private DockMode _dock = DockMode.None;
		
		///<summary>This flag prevents recursive rearrangement.</summary>
		private bool _rearrangementLock = false;

		/// <summary>
		/// Initializes a new instance of the <see cref="ViewNode"/> class.
		/// </summary>
		public ViewNode() {
		}

		#region Properties

		/// <summary>
		/// Gets or sets the location of node relative to its parent. ViewNode location is left-top point of bounding box
		/// </summary>
		/// <value>The location of node.</value>
		public override Vector2 Location {
			get { 
				return base.Location;
			}
			set {
				base.Location = value;
				NotifyBoundsChanged();
			}
		}

		/// <summary>
		/// Gets or sets the size of node.
		/// </summary>
		/// <value>The size of node.</value>
		public Vector2 Size {
			get { return _size; }
			set { 
				_size = value;
				base.Origin = value.Half;
				NotifyBoundsChanged(); 
			}
		}

		/// <summary>
		/// Gets or sets the coordinate X of left node side.
		/// </summary>
		/// <value>The coordinate of left node side.</value>
		public float Left {
			get { return Location.X; }
			set { Location = new Vector2(value, Location.Y); }
		}

		/// <summary>
		/// Gets or sets the coordinate Y of top node side.
		/// </summary>
		/// <value>The coordinate of top node side.</value>
		public float Top {
			get { return Location.Y; }
			set { Location = new Vector2(Location.X, value); }
		}

		/// <summary>
		/// Gets or sets the width of node.
		/// </summary>
		/// <value>The width of node.</value>
		public float Width {
			get { return Size.X; }
			set { Size = new Vector2(value, Size.Y); }
		}

		/// <summary>
		/// Gets or sets the height of node.
		/// </summary>
		/// <value>The height of node.</value>
		public float Height {
			get { return Size.Y; }
			set { Size = new Vector2(Size.X, value); }
		}

		/// <summary>
		/// Gets or sets the dock mode for this view node.
		/// </summary>
		/// <value>The dock mode of view node.</value>
		public DockMode Dock {
			get { return _dock; }
			set {
				_dock = value;
				//should call rearrangement of parent nodes
				ViewNode parent = Parent as ViewNode;
				if (null != parent) {
					parent.RearrangeChildren();
				}
			}
		}

		/// <summary>
		/// Gets the origin point offset of node which is relative to location (like a correction offset). It is always half of size for nodes
		/// </summary>
		/// <value>The origin point offset of node.</value>
		public override Vector2 Origin {
			get { return base.Origin; }
			set {
				throw new NotImplementedException("ViewNode doesn't support change of Origin property. Origin of node is always equals half of size.");
			}
		}

		/// <summary>
		/// Gets or sets the self bounding rect of node (excluding margin)
		/// </summary>
		/// <value>The bounding rect of node.</value>
		public Rect Bounds {
			get { return Rect.FromBox(Location, Size); }
			set {
				//set all of properties silently
				_size = value.Size;
				base.Location = value.LeftTop;				
				base.Origin = _size.Half;
				//notify this event
				NotifyBoundsChanged();
			}
		}

		/// <summary>
		/// Gets or sets the margin around the node. It could be very valuable having deal with layouts.
		/// ViewNode with non-default margin have some extra spacing around.
		/// </summary>
		/// <value>The margin space around this node.</value>
		public Rect Margin {
			get { return _margin; }
			set { 
				_margin = value;
				//notify that bounds were changed somehow
				NotifyBoundsChanged();
			}
		}

		/// <summary>
		/// Gets the outer bounds of node (including margin). These bounds are used by layouts to locate node in proper place
		/// </summary>
		/// <value>The outer bounds of node.</value>
		public Rect OuterBounds {
			get { return Bounds.Inflate(Margin); }
		}

		/// <summary>
		/// Gets the bounding box of view node in its local coordinates.
		/// </summary>
		/// <value>The bounding box of view node in its local coordinates.</value>
		public override Rect BoundingBox {
			get { return new Rect(Vector2.Zero, Size); }
		}

		#region Rearrangment Module

		/// <summary>
		/// Rearranges the child nodes if size is changed or size of some children is changed.
		/// </summary>
		public virtual void RearrangeChildren() {
			//check quit nested rearrangement
			if (!CanRearrangeChildren()) return;

			//set recursion guard flag
			LockRearrangement();

			try {
				Rect clientArea = BoundingBox;
				//all of docked children should be processed
				foreach (ViewNode node in GetDockedWidgets()) {
					DockMode dock = node.Dock;
					if (dock == DockMode.All) {
						node.Bounds = clientArea;
					} else {
						Rect nodeArea = clientArea;
						switch (dock) {
							case DockMode.Left:
								nodeArea.Right = nodeArea.Left + Math.Min(nodeArea.Width, node.Width);
								clientArea.Left = nodeArea.Right;
								break;
							case DockMode.Right:
								nodeArea.Left = nodeArea.Right - Math.Min(nodeArea.Width, node.Width);
								clientArea.Right = nodeArea.Left;
								break;
							case DockMode.Top:
								nodeArea.Bottom = nodeArea.Top + Math.Min(nodeArea.Height, node.Height);
								clientArea.Top = nodeArea.Bottom;
								break;
							case DockMode.Bottom:
								nodeArea.Top = nodeArea.Bottom - Math.Min(nodeArea.Height, node.Height);
								clientArea.Bottom = nodeArea.Top;
								break;
						}
						//place node at the place
						node.Bounds = nodeArea;
					}
				}
			} finally {
				//end rearrangement 
				UnlockRearrangement();
			}
		}

		/// <summary>
		/// Unlocks the rearrangement.
		/// </summary>
		protected void UnlockRearrangement() {
			_rearrangementLock = false;
		}

		/// <summary>
		/// Locks the rearrangement preventing recursive calls.
		/// </summary>
		protected void LockRearrangement() {
			_rearrangementLock = true;
		}

		/// <summary>
		/// Determines whether this instance can rearrange its children.
		/// </summary>
		/// <returns>
		/// 	<c>true</c> if this instance can rearrange its children; otherwise, <c>false</c>.
		/// </returns>
		protected bool CanRearrangeChildren() {
			return !_rearrangementLock;
		}

		/// <summary>
		/// Gets the enumeration of docked child nodes.
		/// </summary>
		/// <returns>Enumeration of docked child nodes.</returns>
		protected IEnumerable<ViewNode> GetDockedWidgets() {
			foreach (Node node in Children) {
				ViewNode viewNode = node as ViewNode;
				if (null != viewNode && viewNode.Dock != DockMode.None) {
					yield return viewNode;
				}
			}
		}

		#endregion

		/// <summary>
		/// Receives notification that node's bounds has been changed
		/// </summary>
		protected void NotifyBoundsChanged() {
			//just rearrange children inside this node
			RearrangeChildren();
			//check if parent is also ViewNode than ask rearrange its children too.
			ViewNode parent = Parent as ViewNode;
			if (null != parent) {
				parent.RearrangeChildren();
			}
		}

		/// <summary>
		/// Performs actions related with adding node to graph
		/// </summary>
		protected override void NotifyNodeAddedToDomain() {
			base.NotifyNodeAddedToDomain();
			NotifyBoundsChanged();
		}

		/// <summary>
		/// Performs actions related with removing node from graph
		/// </summary>
		protected override void NotifyNodeRemovedFromDomain() {
			NotifyBoundsChanged();
			base.NotifyNodeRemovedFromDomain();
		}

		#endregion

		#region Drawing

		/// <summary>
		/// Calls background drawing code for node only
		/// </summary>
		/// <param name="canvas">The canvas to use for node drawing.</param>
		protected override void Draw(Canvas2D canvas) {
			canvas.DrawRect(BoundingBox, ColorU.Red);
		}

		/// <summary>
		/// Draws the node's background. You may easily override this method to customize node background drawing.
		/// </summary>
		/// <param name="canvas">The canvas where to draw.</param>
		/// <param name="region">The background region.</param>
		protected virtual void DrawBackground(Canvas2D canvas, Rect region) {
			StyleDrawer.DrawBackground(canvas, Style, region);
		}

		/// <summary>
		/// Draws the node's foreground. You may easily override this method to customize node foreground drawing.
		/// </summary>
		/// <param name="canvas">The canvas where to draw.</param>
		/// <param name="region">The foreground region.</param>
		protected virtual void DrawForeground(Canvas2D canvas, Rect region) {
			StyleDrawer.DrawForeground(canvas, Style, region);
		}

		/// <summary>
		/// Draws the node's foreground. You may easily override this method to customize node text drawing.
		/// </summary>
		/// <param name="canvas">The canvas where to draw.</param>
		/// <param name="region">The foreground region.</param>
		/// <param name="text">The text to draw.</param>
		protected virtual void DrawText(Canvas2D canvas, Rect region, string text) {
			StyleDrawer.DrawText(canvas, Style, region, text);
		}

		#endregion
	}
}
